package org.oimwrapper.connectors.genericldap;

import com.thortech.util.logging.*;
import java.io.*;
import java.rmi.*;
import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;
import javax.rmi.*;
import javax.security.auth.*;

/**
 * A pool of ldap connections that can be stored in JNDI.
 */
public class OIMLdapConnectionPool implements Serializable, Remote {
	
	/**
	 * The logger instance used by this object.
	 */
	protected Logger logger;
	
	/**
	 * The base name of the JNDI name to store the connection pool as.
	 */
	protected static String jndiNameBase = "genldap-pool-";
	
	/**
	 * A List of the connections in the JNDI pool.
	 */
	protected List connections;

	/**
	 * Default Constructor.
	 * 
	 * @param PoolSize The number of connections to create in the pool.
	 * @param Environment The environment to create the ldap connections with.
	 * @param SecurityContext The security connect to create a privileged ldap connection under, or
	 *                        null if only standard ldap connections are needed.
	 * @param ParentLogger The logger instance that this class should use.
	 * @exception Exception
	 */
	protected OIMLdapConnectionPool(int PoolSize, Hashtable Environment, Subject SecurityContext, Logger ParentLogger) throws Exception {
		logger = ParentLogger;
		logger.debug("Entering ::ldapConnectionPool()");
		connections = new ArrayList();
		logger.debug("Populating a new LDAP connection pool with " + Integer.toString(PoolSize) + " connections.");
		for (int i = 0; i < PoolSize; i++) {
			if (SecurityContext != null) {
				connections.add(Subject.doAs(SecurityContext, new OIMLdapPrivilegedConnection(Environment)));
			} else {
				connections.add(new OIMLdapSerializableContext(Environment, null));
			}
		}
		logger.debug("Exiting ::ldapConnectionPool()");
	}
	
	/**
	 * Gets/creates the pool from JNDI, and fetches the next available connection.
	 * 
	 * @param ObjectClassAttribute The name of the attribute that stores the object class.
	 * @param PoolSize The number of connections to put in the pool if it needs to be created.
	 * @param Environment The environment to use to create the connections in the pool.
	 * @param SecurityContext The security connect to create a privileged ldap connection under, or
	 *                        null if only standard ldap connections are needed.
	 * @param ParentLogger The logger instance that this class should use.
	 * @return The next available LDAP connection.
	 * @exception Exception
	 */
	public static OIMLdapSerializableContext getConnection(String ObjectClassAttribute, int PoolSize, Hashtable Environment, Subject SecurityContext, Logger ParentLogger) throws Exception {
		Context context = new InitialContext(new Properties());
		String jndiName = jndiNameBase;
		jndiName += ((String) Environment.get(Context.PROVIDER_URL)).replaceAll("\\W", "");
		jndiName += ((String) Environment.get(Context.SECURITY_PRINCIPAL)).replaceAll("\\W", "");
		ParentLogger.debug("jndiName = " + jndiName);
		OIMLdapConnectionPool pool= null;
		try {
			pool = (OIMLdapConnectionPool) PortableRemoteObject.narrow(context.lookup(jndiName), OIMLdapConnectionPool.class);
		} catch (Exception e) {
			ParentLogger.debug(e.getClass().getName() + ": " + e.getMessage());
			pool = new OIMLdapConnectionPool(PoolSize, Environment, SecurityContext, ParentLogger);
			context.rebind(jndiName, pool);
		}
		return pool.getConnection(ObjectClassAttribute, jndiName, PoolSize, Environment, SecurityContext);
	}
	
	/**
	 * Gets the next available connection from the pool, and rotates the pool.
	 * 
	 * @param ObjectClassAttribute The name of the attribute that stores the object class.
	 * @param JndiName The name to store the connection pool under in the JNDI tree.
	 * @param PoolSize The number of connections to put in the pool if it needs to be created.
	 * @param Environment The environment to use to create the connections in the pool.
	 * @param SecurityContext The security connect to create a privileged ldap connection under, or
	 *                        null if only standard ldap connections are needed.
	 * @return The next available ldap connection.
	 * @exception Exception
	 */
	protected synchronized OIMLdapSerializableContext getConnection(String ObjectClassAttribute, String JndiName, int PoolSize, Hashtable Environment, Subject SecurityContext) throws Exception {
		OIMLdapSerializableContext result = (OIMLdapSerializableContext) connections.get(0);
		connections.remove(0);
		connections.add(result);
		try {
			SearchControls searchControls = new SearchControls();
			searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
			result.search("", "(" + ObjectClassAttribute + "=*)", searchControls);
			logger.debug("Fetching LDAP connection from pool.");
		} catch (Exception e) {
			logger.debug("Error while testing LDAP connection - " + e.getMessage());
			OIMLdapConnectionPool pool = new OIMLdapConnectionPool(PoolSize, Environment, SecurityContext, logger);
			connections.remove(0);
			connections.add(result);
			result = (OIMLdapSerializableContext) pool.connections.get(0);
			Context context = new InitialContext(new Properties());
			context.rebind(JndiName, pool);
		}
		return result;
	}

	/**
	 * Finalizer to close any remaining connections when the pool is destroyed.
	 * 
	 * @exception Throwable
	 */
	protected void finalize() throws Throwable {
		for (Iterator iterator = connections.listIterator(); iterator.hasNext(); ) {
			OIMLdapSerializableContext ldap = (OIMLdapSerializableContext) iterator.next();
			try {
				ldap.close();
			} catch (Exception e) { ; }
		}
	}
}